// pipeline.cpp
//

#include "pipeline.hpp"
#include "renderengine.hpp"



// Pipeline::Pipeline
//
Pipeline::Pipeline ( RenderEngine &engine,
                     PipelineInfo info )
  : engine(engine),
    info(info)
{
}



// Pipeline::dump_program_infos
//
void Pipeline::dump_program_infos ()
{
  GLT_TRACE("program infos:");

  GLuint pid = program.get_id();

  GLint active_uniforms;
  GLint max_len;
  gl.GetProgramiv(pid, GL_ACTIVE_UNIFORMS, &active_uniforms);
  gl.GetProgramiv(pid, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_len);
  
  GLT_TRACE("  active uniforms: %d", active_uniforms);
  GLchar uname[max_len];
  for (GLuint u = 0; u < GLuint(active_uniforms); u++)
    {
      gl.GetActiveUniformName(pid, u, max_len, nullptr, uname);
      GLT_TRACE("    [%d] = '%s'", u, uname);

      GLint utype, ublkidx;
      gl.GetActiveUniformsiv(pid, 1, &u, GL_UNIFORM_TYPE, &utype);
      gl.GetActiveUniformsiv(pid, 1, &u, GL_UNIFORM_BLOCK_INDEX, &ublkidx);

      GLT_TRACE("      type:          %s", get_gl_type_name(utype).c_str());
      if (ublkidx >= 0)
        {
          GLint uoffset, usize, uvstride, umstride;
          gl.GetActiveUniformsiv(pid, 1, &u, GL_UNIFORM_OFFSET, &uoffset);
          gl.GetActiveUniformsiv(pid, 1, &u, GL_UNIFORM_SIZE, &usize);
          gl.GetActiveUniformsiv(pid, 1, &u, GL_UNIFORM_ARRAY_STRIDE, &uvstride);
          gl.GetActiveUniformsiv(pid, 1, &u, GL_UNIFORM_MATRIX_STRIDE, &umstride);
          
          GLT_TRACE("      block_index:   %d", ublkidx);
          GLT_TRACE("      offset:        %d", uoffset);
          GLT_TRACE("      size:          %d", usize);
          GLT_TRACE("      array_stride:  %d", uvstride);
          GLT_TRACE("      matrix_stride: %d", umstride);
        }
    }
}



// Pipeline::load
//
void Pipeline::load ()
{
  if (loaded)
    return;

  std::string datadir{DATADIR};
  wgl::Shader vertex_shader{GL_VERTEX_SHADER, datadir + "/shaders/" + info.vertex_shader};  
  wgl::Shader fragment_shader{GL_FRAGMENT_SHADER, datadir + "/shaders/" + info.fragment_shader};
  program.attach_shader(vertex_shader);
  program.attach_shader(fragment_shader);
  program.link();

  // dump_program_infos();
  check_components_layout();

  // [FIXME] bind uniform blocks
  {
    GLuint idx;
    idx = gl.GetUniformBlockIndex(program.get_id(), "Matrices");
    if (idx != GL_INVALID_INDEX)
      gl.UniformBlockBinding(program.get_id(), idx, 0);
    idx = gl.GetUniformBlockIndex(program.get_id(), "Lighting");
    if (idx != GL_INVALID_INDEX)
      gl.UniformBlockBinding(program.get_id(), idx, 1);
    idx = gl.GetUniformBlockIndex(program.get_id(), "Material");
    if (idx != GL_INVALID_INDEX)
      gl.UniformBlockBinding(program.get_id(), idx, 2);
  }
  
  loaded = true;
}



// Pipeline::check_components_layout
//
void Pipeline::check_components_layout ()
{
  SDLayout &sd_layout = engine.get_sd_layout();
  GLuint pid = program.get_id();

  GLint active_uniforms;
  GLint max_len;
  gl.GetProgramiv(pid, GL_ACTIVE_UNIFORMS, &active_uniforms);
  gl.GetProgramiv(pid, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_len);
  
  GLT_TRACE("checking components layout (%d active uniforms)",
            active_uniforms);

  GLchar uname[max_len];
  int errs = 0;
  for (GLuint u = 0; u < GLuint(active_uniforms); u++)
    {
      gl.GetActiveUniformName(pid, u, max_len, nullptr, uname);

      GLint utype, ublkidx;
      gl.GetActiveUniformsiv(pid, 1, &u, GL_UNIFORM_TYPE, &utype);
      gl.GetActiveUniformsiv(pid, 1, &u, GL_UNIFORM_BLOCK_INDEX, &ublkidx);
      std::string stype = get_gl_type_name(utype);
      
      if (ublkidx >= 0)
        {
          GLint uoffset, usize, uvstride, umstride;
          gl.GetActiveUniformsiv(pid, 1, &u, GL_UNIFORM_OFFSET, &uoffset);
          gl.GetActiveUniformsiv(pid, 1, &u, GL_UNIFORM_SIZE, &usize);
          gl.GetActiveUniformsiv(pid, 1, &u, GL_UNIFORM_ARRAY_STRIDE, &uvstride);
          gl.GetActiveUniformsiv(pid, 1, &u, GL_UNIFORM_MATRIX_STRIDE, &umstride);
          
          // check
          const SDUniformComponent *comp = sd_layout.lookup_uniform_component(uname);
          if (comp)
            {
              if (GLint(comp->type.gl_type) != utype)
                {
                  // GLT_TRACE("  [%3d] %-30s - ERROR (type=%s!=%s, offset=%d)",
                  //           u, uname, stype.c_str(),
                  //           get_gl_type_name(comp->type.gl_type).c_str(), uoffset);
                  ++errs;
                }
              else if (GLint(comp->offset) != uoffset)
                {
                  // GLT_TRACE("  [%3d] %-30s - ERROR (type=%s, offset=%d!=%d)",
                  //           u, uname, stype.c_str(), uoffset, GLint(comp->offset));
                  ++errs;
                }
              else
                {
                  // GLT_TRACE("  [%3d] %-30s - OK (type=%s, offset=%d)",
                  //           u, uname, stype.c_str(), uoffset);
                }
            }
          else
            {
              GLT_TRACE("  [%3d] %-30s - UNKNOWN (type=%s, offset=%d)",
                        u, uname, stype.c_str(), uoffset);
            }
        }
      else
        {
          GLT_TRACE("  [%3d] %-30s - NOT BLOCK (type=%s)",
                    u, uname, stype.c_str());
        }
    }

  if (errs > 0)
    GLT_FATAL("errors in components layout");
}



// Pipeline::use
//
void Pipeline::use ()
{
  load();
  program.use();

  gl.PolygonMode(GL_FRONT_AND_BACK, info.polygon_mode);

  if (info.depth_test)
    gl.Enable(GL_DEPTH_TEST);
  else
    gl.Disable(GL_DEPTH_TEST);
  
  if (info.textured)
    gl.Enable(GL_TEXTURE_2D);
  else
    gl.Disable(GL_TEXTURE_2D);
}
